/*
 * Anything: A flexible and easy to use data structure that combines the 
 * characteristics of a list and a hashmap into one data structure.
 * 
 * Based on a paper by Peter Sommerlad and Marcel Rueedi on 
 * 'Do-it-yourself reflection' entered at EuroPLOP 1998.
 * 
 * This library was written completely from scratch and is based only 
 * in spirit on the original Java Anything implementation written by 
 * Andre Weinand/Marcel Rueedi in 1997/99 and enhanced by Stefan Tramm 
 * in 2006.
 * 
 * Copyright (c) 2008 Thomas Marti and others. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package any.util;

import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Stack;

import any.Any;
import any.AnyVisitor;
import any.SequentialAny;
import any.incubator.UnknownObject;

public class AnyPrinter implements Cloneable {

	protected Integer indent;
	protected Boolean linebreak;
	protected Boolean fullString;
	protected Integer maxWidth;

	public static AnyPrinter createAnyPrinter() {
		return new AnyPrinter();
	}

	public synchronized AnyVisitor<SequentialAny> createVisitor() {
		return createVisitor(System.out);
	}
	
	public synchronized AnyVisitor<SequentialAny> createVisitor(OutputStream out) {
		return new AnyPrinterVisitor(new PrintWriter(out), this);
	}
	
	public synchronized AnyVisitor<SequentialAny> createVisitor(Writer out) {
		return new AnyPrinterVisitor(new PrintWriter(out), this);		
	}
	
	public synchronized String toString(SequentialAny any) {
		StringWriter writer = new StringWriter();
		any.accept(createVisitor(writer));

		return writer.toString();
	}
	
	public synchronized void print(SequentialAny any, OutputStream out) {
		any.accept(createVisitor(out));
    }
		
	public synchronized void print(SequentialAny any, Writer out) {
		any.accept(createVisitor(out));
	}

	public synchronized void setIndent(Integer indent) {
		this.indent = indent;
	}

	public synchronized void setLinebreak(Boolean linebreak) {
		this.linebreak = linebreak;
	}

	public synchronized void setFullString(Boolean fullString) {
		this.fullString = fullString;
	}

	public synchronized void setMaxWidth(Integer maxWidth) {
		this.maxWidth = maxWidth;
	}

	@Override
	public AnyPrinter clone() {
		try {
			return (AnyPrinter)super.clone();
		} catch (CloneNotSupportedException e) {
			throw new AssertionError(e);
		}
	}
	
	static class AnyPrinterVisitor extends BasePrinterVisitor implements AnyVisitor<SequentialAny> {
		//		private final int maxWidth;
		private final boolean fullString;
		private final Stack<Integer> shift = new Stack<Integer>();
		
		
		AnyPrinterVisitor(PrintWriter out, AnyPrinter printer) {
			super(out);
			shift.push(0);
			indent = (printer.indent != null ? printer.indent : 4);
			linebreak = (printer.linebreak != null ? printer.linebreak : (indent >= 0));
//			maxWidth = (printer.maxWidth != null ? printer.maxWidth : linebreak ? 160 : -1);
			fullString = (printer.fullString != null ? printer.fullString : false);
		}
		public synchronized boolean beginVisit(String key, int index, SequentialAny any) {
			index = recalculateIndex(index);
			indent();
			if (key != null) {
				print("/" + key + " ");
			}
			print("{ ");
			println();
			depth++;
			shift.push(0);
			return true;
		}
		
		public synchronized boolean endVisit(String key, int index, SequentialAny any) {
			index = recalculateIndex(index);
			depth--;
			shift.pop();
			indent();
			print("}");
			if (depth != 0) {
				print(" ");
			}
			println();
			return true;
		}
		
		public synchronized boolean visit(String key, int index, SequentialAny sequentialAny) throws IllegalStateException {
			index = recalculateIndex(index);
			Any any = sequentialAny.toAny();
			String toString;
			
			if (any.getType() == Object.class || any.getType() == UnknownObject.class) {
				UnknownObject unknownObject = getObject(any.toObject());
				if (unknownObject == null) {
					shift.push(shift.pop() + 1);
					return true;
				}
				
				toString = unknownObject.toString();
			} else {
				toString = (fullString ? any.toFullString() : any.toString());				
			}
			
			
			indent();
			if (key != null) {
				//TODO: quote key?
				print("/" + key + " ");
			}
			
			print(toString + " ");
			println();
			
			return true;
		}
		
		private int recalculateIndex(int index) {
			return index - shift.peek();
		}
	}

}
